if ( !window.wala )
  window.wala = { };

/*******************************************************
 *
 * TemplateFactory
 *
 *******************************************************/

wala.TemplateFactory = function( initFunc, destructor, allNodes )
{
  this.initFunc = initFunc;
  this.templateInstanceDtor = destructor;
  this.allNodes = allNodes;
};

wala.TemplateFactory.prototype.createInstance = function( context, parentInstance, component )
{
  if ( !component && parentInstance )
	component = parentInstance.component;
  var obj = new wala.Template( this, context, parentInstance, component );
  obj.factory = this;
  this.initFunc( obj );
  if ( obj.domNodes.length == 0 )
	obj.domNodes = [ document.createTextNode("") ];
  return obj;
};

/*******************************************************
 *
 * Template
 *
 *******************************************************/

/**
 * @param component can be null. It is not null if the template is a root template of the component.
 */
wala.Template = function( factory, context, parentInstance, component )
{
  this.factory = factory;
  this.parentInstance = parentInstance;
  /**
   * If the template has been constructed and defined inside a component then
   * this is the component instance.
   */
  this.component = component;
  /**
   * Holds the parameters passed to the template during construction.
   */
  this.context = context;
  /**
   * ATTENTION: Only useful during template construction. Further updates of the DOM
   * may leave this array outdated.
   * TODO: remove
   */
  this.domNodes = [ ];
  /**
   * List of all expressions created by this template instance.
   */
  this.__expressions = [ ];
};

wala.Template.prototype.dtor = function()
{
  // Destruct the expressions belonging to the template
  for( var i = 0; i < this.__expressions.length; ++i )
	this.__expressions[i].dtor();
  
  this.factory.templateInstanceDtor(this);
};

wala.Template.prototype.processForeach = function( obj, has_index )
{
  var oldNodes = [ ];
  if ( obj.templates.length > 0 )
  {
	for( var i = 0; i < obj.templates.length; ++i )
	{
	  var t = obj.templates[i];
	  t.__mark = true;
	  t.factory.allNodes( t, oldNodes );
	}
  }
  else
	oldNodes = obj.nodes;
  
  var newList = obj.expr.evaluate();
  if ( !newList )
	newList = [ ];
  var newNodes = [ ];
  var newTemplates = [ ];
  for( var i = 0; i < newList.length; ++i )
  {
	var item = newList[i];
	var index = obj.list.indexOf(item);
	if ( index == -1 )
	  var templ = obj.template.createInstance( [ item, i ].concat( obj.context ), this );
	else
	{
	  var templ = obj.templates[index];
	  delete templ.__mark;
	  if ( has_index && i != index && templ.index_change_event )
	  {
		templ.context[1] = i;
		templ.index_change_event.emit();
	  }
	}
	newTemplates.push(templ);
	templ.factory.allNodes( templ, newNodes );
  }
  
  // Destroy the templates which are no longer required
  for( var i = 0; i < obj.templates.length; ++i )
  {
	var templ = obj.templates[i];
	if( templ.__mark )
	{
	  templ.dtor();
	}
  }
  
  if ( newNodes.length == 0 )
	newNodes = [ document.createTextNode("") ];
  
  if ( obj.nodes )
	this.replaceNodes( oldNodes, newNodes );

  obj.nodes = newNodes;
  obj.list = newList;
  obj.templates = newTemplates;
  
  return newNodes;
};

wala.Template.prototype.destructForeach = function( obj )
{
  // Destruct all instantiated templates
  for( var i = 0; i < obj.templates.length; ++i )
  {
	var templ = obj.templates[i]; 
	templ.dtor();
  }
};

wala.Template.prototype.allNodesForeach = function( obj, nodes )
{
  // The list is empty and therefore only a placeholder text node is shown?
  if ( obj.templates.length == 0 )
  {	
	nodes.push(obj.nodes[0]);
  }
  else
  {
	for( var i = 0; i < obj.templates.length; ++i )
	{
	  var templ = obj.templates[i]; 
	  templ.factory.allNodes(templ, nodes);
	}
  }
};

wala.Template.prototype.processIf = function( obj, template, context )
{
  // Perhaps we have already generated HTML and nothing changed?
  if ( obj.templateInstance && obj.templateInstance.factory == template )
	  return;

  // Get hold of the old DOM nodes
  if ( obj.templateInstance )
  {
	var oldNodes = [ ];
	obj.templateInstance.factory.allNodes( obj.templateInstance, oldNodes );
  }
  else
	var oldNodes = obj.nodes;
  
  // Delete the previous template (if there are any)
  if ( obj.templateInstance )
	obj.templateInstance.dtor();  
  
  var newNodes;
  // Instantiate a new template
  if ( template )
  {
	obj.templateInstance = template.createInstance( context, this );
	var newNodes = obj.templateInstance.domNodes;
	if ( newNodes.length == 0 )
	  newNodes = [ document.createTextNode("") ];
  }
  // No new template, just insert a place holder
  else
  {
	delete obj.templateInstance;
	newNodes = [ document.createTextNode("") ];
  }
    
  if ( oldNodes )
	this.replaceNodes( oldNodes, newNodes );
  
  obj.nodes = newNodes;
  return newNodes;
};

wala.Template.prototype.destructIf = function( obj )
{
  // No template instantiated?
  if ( !obj.templateInstance )
	return;
  
  // Destruct the instantiated template
  obj.templateInstance.dtor();  
};

wala.Template.prototype.allNodesIf = function( obj, nodes )
{
  // If no template has been instantiated, then only a placeholder text node is shown
  if ( !obj.templateInstance )
  {
	nodes.push( obj.nodes[0] );
	return;
  }
  
  // Destruct all instantiated templates
  obj.templateInstance.factory.allNodes(obj.templateInstance, nodes);
};

wala.Template.prototype.processTemplateChange = function(templateInstance, newTemplateFactory)
{
  // Nothing changed?
  if ( templateInstance.factory == newTemplateFactory )
	return;
    
  // Instantiate the new template
  var newTemplate = newTemplateFactory.createInstance(templateInstance.context, this );
  
  // Destroy the old template instance
  if ( templateInstance )
	templateInstance.dtor();

  // Replace the old DOM nodes with the new DOM nodes
  if ( templateInstance )
  {
	var oldNodes = [];
	templateInstance.factory.allNodes( templateInstance, oldNodes );
	this.replaceNodes( oldNodes, newTemplate.domNodes );
  }
  
  return newTemplate;
};

wala.Template.prototype.replaceNodes = function( oldNodes, newNodes )
{
  var parentDomNode = oldNodes[0].parentNode;
  var current = null;
	
  // Delete old nodes if they are no longer in the set of nodes
  for( var i = 0; i < oldNodes.length; ++i )
  {
	var n = oldNodes[i];
	if ( newNodes.indexOf( n ) == -1 )
	{
	  if ( n.parentNode )
	  {
		current = n.nextSibling;
		parentDomNode.removeChild( n );
	  }
	}
	else if ( !current )
	  current = n;
  }
  
  for( var i = 0; i < newNodes.length; ++i )
  {
	// The node is already at the required position?
	if ( current == newNodes[i] )
	{
	  current = current.nextSibling;
	  continue;
	}
	// This is a new node. Insert it
	parentDomNode.insertBefore( newNodes[i], current );
  }
  
  return newNodes;
};

wala.Template.prototype.getIndexAndListen = function(func, obj)
{
  if ( !this.index_change_event )
	this.index_change_event = new JSOT.Event(this);
  this.index_change_event.addListenerOnce( func, obj );
  return this.context[1];
}
